// 19.1 控制内存分配
/**
 * 某些应用程序对内存分配有特殊的需求，因此我们无法将标准内存管理机制直接应用于这些程序。它们常常需要自定义内存分配的细节，比如使用关键字new将对象放置在特定的内存空间中。
 *   为了实现这一目的，应用程序需要重载new运算符和delete运算符以控制内存分配的过程。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include <functional>
#include "../VisualStudio2012/15/Quote.h"
#include "../VisualStudio2012/12/TextQuery.h"
#include <tuple>
#include <bitset>
#include <regex>
#include <random>
#include <cmath>
#include <iomanip>
#include <cstdio>
#include <cstdlib>

int main()
{
    // 19.1.1 重载new和delete
    /*尽管我们说能够“重载new和delete”，但是实际上重载这两个运算符与重载其他运算符的过程大不相同。要想真正掌握重载new和delete的方法，首先要对new表达式和delete表达式的工作机理有更多了解。
      当我们使用一条new表达式时：[插图]
// new 表达式
string *sp = new string("a value"); // 分配并初始化一个string对象
string *arr = new string[10];       // 分配10个默认初始化的string对象
      实际执行了三步操作。第一步，new表达式调用一个名为operator new（或者operator new[]）的标准库函数。该函数分配一块足够大的、原始的、未命名的内存空间以便存储特定类型的对象（或者对象的数组）。
        第二步，编译器运行相应的构造函数以构造这些对象，并为其传入初始值。第三步，对象被分配了空间并构造完成，返回一个指向该对象的指针。
      当我们使用一条delete表达式删除一个动态分配的对象时：[插图]
delete sp;     // 销毁*sp，然后释放sp指向的内存空间
delete [] arr; // 销毁数组中的元素，然后释放对应的内存空间
      实际执行了两步操作。第一步，对sp所指的对象或者arr所指的数组中的元素执行对应的析构函数。第二步，编译器调用名为operator delete（或者operator delete[ ]）的标准库函数释放内存空间。
      当自定义了全局的operator new函数和operator delete函数后，我们就担负起了控制动态内存分配的职责。这两个函数必须是正确的：因为它们是程序整个处理过程中至关重要的一部分。
      应用程序可以在全局作用域中定义operator new函数和operator delete函数，也可以将它们定义为成员函数。当编译器发现一条new表达式或delete表达式后，将在程序中查找可供调用的operator函数。
        如果被分配（释放）的对象是类类型，则编译器首先在类及其基类的作用域中查找。此时如果该类含有operator new成员或operator delete成员，则相应的表达式将调用这些成员。
        否则，编译器在全局作用域查找匹配的函数。此时如果编译器找到了用户自定义的版本，则使用该版本执行new表达式或delete表达式；如果没找到，则使用标准库定义的版本。
      我们可以使用作用域运算符令new表达式或delete表达式忽略定义在类中的函数，直接执行全局作用域中的版本。例如，：：new只在全局作用域中查找匹配的operator new函数，：：delete与之类似。
    */

    // operator new接口和operator delete接口
    /*标准库定义了operator new函数和operator delete函数的8个重载版本。其中前4个版本可能抛出bad_alloc异常，后4个版本则不会抛出异常：[插图]
// 这些版本可能抛出异常
void *operator new(size_t);              // 分配一个对象
void *operator new[](size_t);            // 分配一个数组
void *operator delete(void*) noexcept;   // 释放一个对象
void *operator delete[](void*) noexcept; // 释放一个数组
// 这些版本承诺不会抛出异常，参见12.1.2节（第409页）
void *operator new(size_t, nothrow_t&) noexcept;
void *operator new[](size_t, nothrow_t&) noexcept;
void *operator delete(void*, nothrow_t&) noexcept;
void *operator delete[](void*, nothrow_t&) noexcept;
      类型nothrow_t是定义在new头文件中的一个struct，在这个类型中不包含任何成员。new头文件还定义了一个名为nothrow的const对象，用户可以通过这个对象请求new的非抛出版本（参见12.1.2节，第408页）。
        与析构函数类似，operator delete也不允许抛出异常（参见18.1.1节，第685页）。当我们重载这些运算符时，必须使用noexcept异常说明符（参见18.1.4节，第690页）指定其不抛出异常。
      应用程序可以自定义上面函数版本中的任意一个，前提是自定义的版本必须位于全局作用域或者类作用域中。当我们将上述运算符函数定义成类的成员时，它们是隐式静态的（参见7.6节，第270页）。
        我们无须显式地声明static，当然这么做也不会引发错误。因为operator new用在对象构造之前而operator delete用在对象销毁之后，所以这两个成员（new和delete）必须是静态的，
        而且它们不能操纵类的任何数据成员。
      当编译器调用operator new时，把存储指定类型对象所需的字节数传给size_t形参；当调用operator new[]时，传入函数的则是存储数组中所有元素所需的空间。
      如果我们想要自定义operator new函数，则可以为它提供额外的形参。此时，用到这些自定义函数的new表达式必须使用new的定位形式（参见12.1.2节，第409页）将实参传给新增的形参。
        尽管在一般情况下我们可以自定义具有任何形参的operator new，但是下面这个函数却无论如何不能被用户重载：[插图]
void* operator new(size_t, void*); // 不允许重新定义这个版本
        这种形式只供标准库使用，不能被用户重新定义。
      对于operator delete函数或者operator delete[]函数来说，它们的返回类型必须是void，第一个形参的类型必须是void＊。
        执行一条delete表达式将调用相应的operator函数，并用指向待释放内存的指针来初始化void＊形参。
      当我们将operator delete或operator delete[]定义成类的成员时，该函数可以包含另外一个类型为size_t的形参。此时，该形参的初始值是第一个形参所指对象的字节数。
        size_t形参可用于删除继承体系中的对象。如果基类有一个虚析构函数（参见15.7.1节，第552页），则传递给operator delete的字节数将因待删除指针所指对象的动态类型不同而有所区别。
        而且，实际运行的operator delete函数版本也由对象的动态类型决定。
    */

    // 术语：new表达式与operator new函数
    /*标准库函数operator new和operator delete的名字容易让人误解。和其他operator函数不同（比如operator=），这两个函数并没有重载new表达式或delete表达式。
        实际上，我们根本无法自定义new表达式或delete表达式的行为。
      一条new表达式的执行过程总是先调用operator new函数以获取内存空间，然后在得到的内存空间中构造对象。
        与之相反，一条delete表达式的执行过程总是先销毁对象，然后调用operator delete函数释放对象所占的空间。
      我们提供新的operator new函数和operator delete函数的目的在于改变内存分配的方式，但是不管怎样，我们都不能改变new运算符和delete运算符的基本含义。
    */

    // malloc函数与free函数
    /*当你定义了自己的全局operator new和operator delete后，这两个函数必须以某种方式执行分配内存与释放内存的操作。
        也许你的初衷仅仅是使用一个特殊定制的内存分配器，但是这两个函数还应该同时满足某些测试的目的，即检验其分配内存的方式是否与常规方式类似。
        为此，我们可以使用名为malloc和free的函数，C++从C语言中继承了这些函数，并将其定义在cstdlib头文件中。
      malloc函数接受一个表示待分配字节数的size_t，返回指向分配空间的指针或者返回0以表示分配失败。
        free函数接受一个void＊，它是malloc返回的指针的副本，free将相关内存返回给系统。调用free（0）没有任何意义。
      如下所示是编写operator new和operator delete的一种简单方式，其他版本与之类似：[插图]
void* operator new(size_t size) {
  if(void* mem = malloc(size))
    return mem;
  else
    throw bad_alloc();
}
void operator delete(void* mem) noexcept { free(mem); }
    */

    // 19.1.2 定位new表达式
    /*尽管operator new函数和operator delete函数一般用于new表达式，然而它们毕竟是标准库的两个普通函数，因此普通的代码也可以直接调用它们。
      在C++的早期版本中，allocator类（参见12.2.2节，第427页）还不是标准库的一部分。应用程序如果想把内存分配与初始化分离开来的话，需要调用operator new和operator delete。
        这两个函数的行为与allocator的allocate成员和deallocate成员非常类似，它们负责分配或释放内存空间，但是不会构造或销毁对象。
      与allocator不同的是，对于operator new分配的内存空间来说我们无法使用construct函数构造对象。相反，我们应该使用new的定位new（placement new）形式（参见12.1.2节，第409页）构造对象。
        如我们所知，new的这种形式为分配函数提供了额外的信息。我们可以使用定位new传递一个地址，此时定位new的形式如下所示：[插图]
new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] { braced initializer list }
        其中place_address必须是一个指针，同时在initializers中提供一个（可能为空的）以逗号分隔的初始值列表，该初始值列表将用于构造新分配的对象。
      当仅通过一个地址值调用时，定位new使用operator new（size_t，void＊）“分配”它的内存。这是一个我们无法自定义的operator new版本（参见19.1.1节，第727页）。
        该函数不分配任何内存，它只是简单地返回指针实参；然后由new表达式负责在指定的地址初始化对象以完成整个工作。
        事实上，定位new允许我们在一个特定的、预先分配的内存地址上构造对象。
      当只传入一个指针类型的实参时，定位new表达式构造对象但是不分配内存。
    */

    // 显式的析构函数调用
    /*就像定位new与使用allocate类似一样，对析构函数的显式调用也与使用destroy很类似。我们既可以通过对象调用析构函数，也可以通过对象的指针或引用调用析构函数，这与调用其他成员函数没什么区别：[插图]
string *sp = new string("a value"); // 分配并初始化一个string对象
sp->~string(); // 调用析构函数
      和调用destroy类似，调用析构函数可以清除给定的对象但是不会释放该对象所在的空间。如果需要的话，我们可以重新使用该空间。
      调用析构函数会销毁对象，但是不会释放内存。
    */


    return 0;
}